
package org.spp.batchedAccess;

import java.util.Collections;
import java.util.Random;
import java.util.List;
import java.util.ArrayList;
import java.io.PrintWriter;
import java.io.FileWriter;
import java.io.IOException;

import org.spp.utilities.comparators.ScoreComparator;
import org.spp.utilities.SetUtils;
import org.spp.voronoi.Centroid;
import org.spp.voronoi.Vertex;
import org.spp.voronoi.Centroid3D;
import org.spp.voronoi.Centroid2D;

/**
 * Invoker.java
 * @author Ilio Catallo, Eleonora Ciceri
 * @version 1.0
 * 
 * Description: the class simulates an invoker that retrieves objects either in
 * score based acess or in distance based access
 * 
 * Date: Jul 11, 2011
 */
public class Invoker {
    
    // Attributes
    private int N;
    private List<Centroid> objects;
    private long seed;
    private Random randGen;
    
    /**
     * Constructor for the Invoker
     * @param N
     * @param seed
     */    
    public Invoker(int N, long seed, int dimension){
    
        this.N = N;
        this.seed = seed;
        
        this.randGen = new Random(this.seed);
        createSyntheticData(dimension);
        
        Collections.sort(this.objects, new ScoreComparator());    
    }
    
    /**
     * Method that returns the objects generated by the invoker
     * @return objects
     */    
    public List<Centroid> getObjects() {
        return objects;
    }
    
    /**
     * Method that retrieves the object with the higher score
     * @return index
     */     
    public int retrieveMaxScorePosition() {
        // Variables
        int index = 0;
        Double max = Double.NEGATIVE_INFINITY;
        
        for (int i = 0; i < objects.size(); i++)
            if (objects.get(i).getScore() > max) 
                index = i;
        
        return index;
    }
   
    /**
     * Method that returns the i-th object
     * @param index
     * @return centroid
     */     
    public Centroid pullObjectByIndex(int index){        
        return objects.get(index);        
    }
    
    /**
     * Method that returns a random object
     * @return centroid
     */     
    public Centroid pullObjectRandom(){  
        int index = (int)Math.floor(randGen.nextDouble()*1000);
        
        return pullObjectByIndex(index);  
    }
    
    /**
     * Method that returns an object retrieved with score based access
     * @param Sdepth 
     * @return centroid
     */     
    public Centroid pullObjectByScore(int Sdepth) {
        return objects.get(Sdepth);
    }
    
    /**
     * Method that returns an objects retrieves with distance based access
     * @param vertex
     * @param radius
     * @return centroid
     */     
    public List<Centroid> pullObjectByDistance(Vertex vertex, Double radius) {
        // Variables
        List<Centroid> neighbors = new ArrayList<Centroid>();
        
        for (int i = 0; i < objects.size(); i++) {
            double distance = objects.get(i).computeDistance(vertex);
            
            if (distance <= radius)
                neighbors.add(objects.get(i));
        }
        
        return neighbors;
    }
    
    /**
     * Method that computes the diversity score for each object in the pool
     * @param lambda
     * @param centroids
     */     
    public void computeDivScores(double lambda, List<Centroid> centroids) {
        for (int i = 0; i < objects.size(); i++)
            objects.get(i).computeDivScore(lambda, centroids);
    }
    
    /**
     * Method that computes the objective function for the calling algorithm
     * @param selectedObjs 
     * @param lambda 
     * @return objectiveFunction
     */     
    public double computeObjectiveFunction(List<Centroid> selectedObjs, double lambda){
        // Variables
        List<Double> scores = new ArrayList<Double>();
        List<Double> distances = new ArrayList<Double>();
        
        for (int i = 0; i < selectedObjs.size(); i++){
            Centroid selectedObj = selectedObjs.get(i);
            List<Centroid> selectedObjsTemp = new ArrayList<Centroid>();
           
            selectedObjsTemp.addAll(selectedObjs);
            selectedObjsTemp.remove(selectedObj);
            
            scores.add(selectedObj.getScore());
            
            Double minDist = Double.POSITIVE_INFINITY;
            
            for (Centroid selectedObjTemp: selectedObjsTemp)
                if (selectedObj.computeDistance(selectedObjTemp) < minDist)
                    minDist = selectedObj.computeDistance(selectedObjTemp);
            
            distances.add(minDist);            
        }
        
        double FO = (1-lambda)*SetUtils.mean(scores) + lambda*SetUtils.min(distances);
        return FO;
    }    
    
    /**
     * Method that enables the construction of synthetic data
     */     
    private void createSyntheticData(int dimension){
        // Variables
        double upperBound = 0.5;
        
        // Construction of the objects
        objects = new ArrayList<Centroid>();
        for (int i = 0; i < N; i++) {
            double[] coordinates = new double[dimension];
            for (int j = 0; j < dimension; j++)
                coordinates[j] = (randGen.nextDouble() - upperBound);
            
            double score = randGen.nextDouble();
            
            if (dimension == 2)
                objects.add(new Centroid2D(coordinates, score, i+1));                
            else
                objects.add(new Centroid3D(coordinates, score, i+1));
        }
        System.out.println("Synthetic objects created. Number of objects: " + objects.size());
        flushObjectsToFile();
    }
    
    /**
     * Method that writes the objects and the scores onto a file (so that they
     * can be imported into MATLAB for testing)
     */  
    private void flushObjectsToFile(){       
        // Variables
        int dimension = objects.get(0).getCoordinates().length;
        try{
            //Open the file on disk
            PrintWriter scoresFile = new PrintWriter(new FileWriter("scores"));
            PrintWriter objectsFile = new PrintWriter(new FileWriter("objects"));
            
            //Write the file on disk
            for (int i = 0; i < objects.size(); i++){
                double[] coordinates = objects.get(i).getCoordinates();

                if (dimension == 3)
                    objectsFile.println(Double.toString(coordinates[0]) + " " + Double.toString(coordinates[1]) + " " + Double.toString(coordinates[2]));
                else
                    objectsFile.println(Double.toString(coordinates[0]) + " " + Double.toString(coordinates[1]));
                
                scoresFile.println(Double.toString(objects.get(i).getScore()));
            }  
            
            scoresFile.close();
            objectsFile.close();
        } 
        catch (IOException e) {e.printStackTrace();} 
    }
    
}
